// 版权归Epic Games, Inc.所有。保留所有权利。

#include "IndicatorDescriptor.h"

#include "Engine/LocalPlayer.h"
#include "SceneView.h"
#include "UI/IndicatorSystem/LyraIndicatorManagerComponent.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(IndicatorDescriptor)

/**
 * 投影函数，将指示器描述符投影到屏幕上
 * @param IndicatorDescriptor 指示器描述符
 * @param InProjectionData 场景视图投影数据
 * @param ScreenSize 屏幕尺寸
 * @param OutScreenPositionWithDepth 输出的带深度的屏幕位置
 * @return 投影是否成功
 */
bool FIndicatorProjection::Project(const UIndicatorDescriptor& IndicatorDescriptor, const FSceneViewProjectionData& InProjectionData, const FVector2f& ScreenSize, FVector& OutScreenPositionWithDepth)
{
	// 获取场景组件
	if (USceneComponent* Component = IndicatorDescriptor.GetSceneComponent())
	{
		TOptional<FVector> WorldLocation; // 世界位置可选值
		// 如果组件插槽名称不为None，使用插槽变换位置
		if (IndicatorDescriptor.GetComponentSocketName() != NAME_None)
		{
			WorldLocation = Component->GetSocketTransform(IndicatorDescriptor.GetComponentSocketName()).GetLocation();
		}
		else
		{
			// 否则使用组件位置
			WorldLocation = Component->GetComponentLocation();
		}

		// 计算投影世界位置，加上世界位置偏移
		const FVector ProjectWorldLocation = WorldLocation.GetValue() + IndicatorDescriptor.GetWorldPositionOffset();
		// 获取投影模式
		const EActorCanvasProjectionMode ProjectionMode = IndicatorDescriptor.GetProjectionMode();
		
		// 根据投影模式进行不同的投影计算
		switch (ProjectionMode)
		{
			case EActorCanvasProjectionMode::ComponentPoint: // 组件点投影
			{
				if (WorldLocation.IsSet())
				{
					FVector2D OutScreenSpacePosition; // 输出屏幕空间位置
					// 获取像素点位置，判断是否在相机前方
					const bool bInFrontOfCamera = ULocalPlayer::GetPixelPoint(InProjectionData, ProjectWorldLocation, OutScreenSpacePosition, &ScreenSize);

					// 根据是否在相机前方调整X偏移方向
					OutScreenSpacePosition.X += IndicatorDescriptor.GetScreenSpaceOffset().X * (bInFrontOfCamera ? 1 : -1);
					OutScreenSpacePosition.Y += IndicatorDescriptor.GetScreenSpaceOffset().Y; // 添加Y偏移

					// 如果不在相机前方但在屏幕内，调整位置到屏幕边缘
					if (!bInFrontOfCamera && FBox2f(FVector2f::Zero(), ScreenSize).IsInside((FVector2f)OutScreenSpacePosition))
					{
						const FVector2f CenterToPosition = (FVector2f(OutScreenSpacePosition) - (ScreenSize / 2)).GetSafeNormal(); // 计算中心到位置的方向
						OutScreenSpacePosition = FVector2D((ScreenSize / 2) + CenterToPosition * ScreenSize); // 调整到屏幕边缘
					}

					// 设置输出的带深度的屏幕位置，深度为视图原点到投影世界位置的距离
					OutScreenPositionWithDepth = FVector(OutScreenSpacePosition.X, OutScreenSpacePosition.Y, FVector::Dist(InProjectionData.ViewOrigin, ProjectWorldLocation));

					return true; // 投影成功
				}

				return false; // 投影失败
			}
			case EActorCanvasProjectionMode::ComponentScreenBoundingBox: // 组件屏幕边界框投影
			case EActorCanvasProjectionMode::ActorScreenBoundingBox: // 角色屏幕边界框投影
			{
				FBox IndicatorBox; // 指示器边界框
				// 根据投影模式选择边界框
				if (ProjectionMode == EActorCanvasProjectionMode::ActorScreenBoundingBox)
				{
					IndicatorBox = Component->GetOwner()->GetComponentsBoundingBox(); // 获取角色的所有组件边界框
				}
				else
				{
					IndicatorBox = Component->Bounds.GetBox(); // 获取组件边界框
				}

				FVector2D LL, UR; // 左下和右上坐标
				// 获取像素边界框，判断是否在相机前方
				const bool bInFrontOfCamera = ULocalPlayer::GetPixelBoundingBox(InProjectionData, IndicatorBox, LL, UR, &ScreenSize);
			
				// 获取边界框锚点和屏幕空间偏移
				const FVector& BoundingBoxAnchor = IndicatorDescriptor.GetBoundingBoxAnchor();
				const FVector2D& ScreenSpaceOffset = IndicatorDescriptor.GetScreenSpaceOffset();

				FVector ScreenPositionWithDepth; // 带深度的屏幕位置
				// 根据边界框锚点插值计算X坐标，并根据是否在相机前方调整X偏移方向
				ScreenPositionWithDepth.X = FMath::Lerp(LL.X, UR.X, BoundingBoxAnchor.X) + ScreenSpaceOffset.X * (bInFrontOfCamera ? 1 : -1);
				ScreenPositionWithDepth.Y = FMath::Lerp(LL.Y, UR.Y, BoundingBoxAnchor.Y) + ScreenSpaceOffset.Y; // 计算Y坐标
				ScreenPositionWithDepth.Z = FVector::Dist(InProjectionData.ViewOrigin, ProjectWorldLocation); // 计算深度

				const FVector2f ScreenSpacePosition = FVector2f(FVector2D(ScreenPositionWithDepth)); // 屏幕空间位置
				// 如果不在相机前方但在屏幕内，调整位置到屏幕边缘
				if (!bInFrontOfCamera && FBox2f(FVector2f::Zero(), ScreenSize).IsInside(ScreenSpacePosition))
				{
					const FVector2f CenterToPosition = (ScreenSpacePosition - (ScreenSize / 2)).GetSafeNormal(); // 计算中心到位置的方向
					const FVector2f ScreenPositionFromBehind = (ScreenSize / 2) + CenterToPosition * ScreenSize; // 从后方计算的屏幕位置
					ScreenPositionWithDepth.X = ScreenPositionFromBehind.X; // 设置X坐标
					ScreenPositionWithDepth.Y = ScreenPositionFromBehind.Y; // 设置Y坐标
				}
				
				OutScreenPositionWithDepth = ScreenPositionWithDepth; // 设置输出
				return true; // 投影成功
			}
			case EActorCanvasProjectionMode::ActorBoundingBox: // 角色边界框投影
			case EActorCanvasProjectionMode::ComponentBoundingBox: // 组件边界框投影
			{
				FBox IndicatorBox; // 指示器边界框
				// 根据投影模式选择边界框
				if (ProjectionMode == EActorCanvasProjectionMode::ActorBoundingBox)
				{
					IndicatorBox = Component->GetOwner()->GetComponentsBoundingBox(); // 获取角色的所有组件边界框
				}
				else
				{
					IndicatorBox = Component->Bounds.GetBox(); // 获取组件边界框
				}

				// 计算投影框点，根据边界框锚点调整位置
				const FVector ProjectBoxPoint = IndicatorBox.GetCenter() + (IndicatorBox.GetSize() * (IndicatorDescriptor.GetBoundingBoxAnchor() - FVector(0.5)));

				FVector2D OutScreenSpacePosition; // 输出屏幕空间位置
				// 获取像素点位置，判断是否在相机前方
				const bool bInFrontOfCamera = ULocalPlayer::GetPixelPoint(InProjectionData, ProjectBoxPoint, OutScreenSpacePosition, &ScreenSize);
				// 根据是否在相机前方调整X偏移方向
				OutScreenSpacePosition.X += IndicatorDescriptor.GetScreenSpaceOffset().X * (bInFrontOfCamera ? 1 : -1);
				OutScreenSpacePosition.Y += IndicatorDescriptor.GetScreenSpaceOffset().Y; // 添加Y偏移

				// 如果不在相机前方但在屏幕内，调整位置到屏幕边缘
				if (!bInFrontOfCamera && FBox2f(FVector2f::Zero(), ScreenSize).IsInside((FVector2f)OutScreenSpacePosition))
				{
					const FVector2f CenterToPosition = (FVector2f(OutScreenSpacePosition) - (ScreenSize / 2)).GetSafeNormal(); // 计算中心到位置的方向
					OutScreenSpacePosition = FVector2D((ScreenSize / 2) + CenterToPosition * ScreenSize); // 调整到屏幕边缘
				}

				// 设置输出的带深度的屏幕位置，深度为视图原点到投影框点的距离
				OutScreenPositionWithDepth = FVector(OutScreenSpacePosition.X, OutScreenSpacePosition.Y, FVector::Dist(InProjectionData.ViewOrigin, ProjectBoxPoint));
					
				return true; // 投影成功
			}
		}
	}

	return false; // 投影失败
}

/**
 * 设置指示器管理器组件
 * @param InManager 指示器管理器组件
 */
void UIndicatorDescriptor::SetIndicatorManagerComponent(ULyraIndicatorManagerComponent* InManager)
{
	// 确保没有人设置过这个值
	if (ensure(ManagerPtr.IsExplicitlyNull()))
	{
		ManagerPtr = InManager; // 设置管理器指针
	}
}

/**
 * 注销指示器
 */
void UIndicatorDescriptor::UnregisterIndicator()
{
	// 如果管理器存在，移除该指示器
	if (ULyraIndicatorManagerComponent* Manager = ManagerPtr.Get())
	{
		Manager->RemoveIndicator(this); // 移除指示器
	}
}